
#include "pci.h"
#include "io.h"

static UCHAR pci_conf_type = PCI_CONF_TYPE_NONE;

#if 0
static VOID   PCIListController(VOID);
static ULONG PCIGetName(unsigned int v, unsigned int d, unsigned char *out_char, int out_size);
#endif

//
// See http://www.cs.ucla.edu/~kohler/class/aos-f04/ref/hardware/vgadoc/PCI.TXT
//

VOID PCIInit()
{
  UCHAR tmp1, tmp2;

  IoWritePortByte(0xCF8, 0);
  IoWritePortByte(0xCFA, 0);

  tmp1 = IoReadPortByte(0xCF8);
  tmp2 = IoReadPortByte(0xCFA);

  if (tmp1 == 0 && tmp2 == 0) {
    pci_conf_type = PCI_CONF_TYPE_2;
  } else {
    ULONG tmplong1, tmplong2;

    tmplong1 = IoReadPortDword(0xCF8);
    IoWritePortDword(0xCF8, 0x80000000);

    tmplong2 = IoReadPortDword(0xCF8);
    IoWritePortDword(0xCF8, tmplong1);

    if (tmplong2 == 0x80000000) {
      pci_conf_type = PCI_CONF_TYPE_1;
    } else {
      pci_conf_type = PCI_CONF_TYPE_NONE;
    }
  }
}

static int PCIConfRead(unsigned bus, unsigned dev, unsigned fn, \
                       unsigned reg, unsigned len, unsigned int *value)
{
  int result;

  if (!value || (bus > 255) || (dev > 31) || (fn > 7) || (reg > 255))
    return -1;

  result = -1;

  switch (pci_conf_type) {
  case PCI_CONF_TYPE_1:
    IoWritePortDword(0xCF8, PCI_CONF1_ADDRESS(bus, dev, fn, reg));

    switch(len) {
    case 1:  *value = IoReadPortByte (0xCFC + (reg & 3)); result = 0; break;
    case 2:  *value = IoReadPortWord (0xCFC + (reg & 2)); result = 0; break;
    case 4:  *value = IoReadPortDword(0xCFC); result = 0; break;
    }
    break;

  case PCI_CONF_TYPE_2:
    IoWritePortByte(0xCF8, 0xF0 | (fn << 1));
    IoWritePortByte(0xCFA, (unsigned char) bus);

    switch(len) {
    case 1:  *value = IoReadPortByte (PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
    case 2:  *value = IoReadPortWord (PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
    case 4:  *value = IoReadPortDword(PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
    }

    IoWritePortByte(0xCF8, 0);
    break;
  }

  return result;
}

struct pcidump_info {
    int offset; /* the data item to retrieve */
    unsigned long bitmask;
    int bool; /* true or false */
    char *string;
};

#define ONEBYTE 0xFF
#define TWOBYTES 0xFFFF
#define FOURBYTES 0xFFFFFFFF

struct pcidump_info dumpinfo[] = {
    {0,                    0,                       0, "\tCompulsory registers:\n"},
    {PCI_VENDOR_ID,        TWOBYTES,                0, "Vendor id: %04x\n"},
    {PCI_DEVICE_ID,        TWOBYTES,                0, "Device id: %04x\n"},
    {PCI_COMMAND,          PCI_COMMAND_IO,          1, "I/O space enabled: %c\n"},
    {PCI_COMMAND,          PCI_COMMAND_MEMORY,      1, "Memory enabled: %c\n"},
    {PCI_COMMAND,          PCI_COMMAND_MASTER,      1, "Master enabled: %c\n"},
    {PCI_COMMAND,          PCI_COMMAND_SPECIAL,     1, "Special command enabled: %c\n"},
    {PCI_COMMAND,          PCI_COMMAND_INVALIDATE,  1, "Write-invalidate enabled: %c\n"},
    {PCI_COMMAND,          PCI_COMMAND_VGA_PALETTE, 1, "Palette-snoop enabled: %c\n"},
    {PCI_COMMAND,          PCI_COMMAND_PARITY,      1, "Parity enabled: %c\n"},
    {PCI_COMMAND,          PCI_COMMAND_WAIT,        1, "Wait-cycle enabled: %c\n"},
    {PCI_COMMAND,          PCI_COMMAND_SERR,        1, "System-error enabled: %c\n"},
    {PCI_COMMAND,          PCI_COMMAND_FAST_BACK,   1, "Fast-back-to-back enabled: %c\n"},
    {PCI_STATUS,           PCI_STATUS_66MHZ,        1, "Can do 66MHz: %c\n"},
    {PCI_STATUS,           PCI_STATUS_UDF,          1, "Supports User Defined Features: %c\n"},
    {PCI_STATUS,           PCI_STATUS_FAST_BACK,    1, "Fast-back-to-back capable: %c\n"},
    {PCI_STATUS,           PCI_STATUS_PARITY,        1, "Data Parity Reported: %c\n"},
    {PCI_STATUS,           PCI_STATUS_DEVSEL_MASK,    0, "Device Select Timing bits are %x\n"},
    {PCI_STATUS,           PCI_STATUS_SIG_TARGET_ABORT,1, "Signalled abort as target: %c\n"},
    {PCI_STATUS,           PCI_STATUS_REC_TARGET_ABORT, 1, "Received abort from target: %c\n"},
    {PCI_STATUS,           PCI_STATUS_REC_MASTER_ABORT, 1, "Aborted transaction as master: %c\n"},
    {PCI_STATUS,           PCI_STATUS_SIG_SYSTEM_ERROR, 1, "Did send a system error: %c\n"},
    {PCI_STATUS,           PCI_STATUS_DETECTED_PARITY, 1, "Detected a parity error: %c\n"},
    {PCI_REVISION_ID,      ONEBYTE,                   0, "Revision id (decimal): %i\n"},
    {PCI_CLASS_PROG,       ONEBYTE,                  0, "Programmer Interface: %02x\n"},
    {PCI_CLASS_DEVICE,     TWOBYTES,                0, "Class of device: %04x\n"},
    {PCI_HEADER_TYPE,      0x7f,                    0, "Header type: %02x\n"},
    {PCI_HEADER_TYPE,      0x80,                    1, "Multi function device: %c\n"},
    {0,                    0,                       0, "\tOptional registers:\n"},
    {PCI_CACHE_LINE_SIZE,  ONEBYTE,                 0, "Cache line size (decimal): %i\n"},
    {PCI_LATENCY_TIMER,    ONEBYTE,                 0, "Latency timer (decimal): %i\n"},
    {PCI_BIST,             PCI_BIST_CAPABLE,        1, "Is Built-In-Self-Test available: %c\n"},
    {PCI_BIST,             PCI_BIST_CODE_MASK,      1, "Did Built-In-Self-Test fail: %c\n"},
    {PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_MEM_MASK,  0, "Base Address 0: %08x\n"},
    {PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_SPACE,      1, "Base Address 0 Is I/O: %c\n"},
    {PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_MEM_TYPE_64, 1, "Base Address 0 is 64-bits: %c\n"},
    {PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_MEM_TYPE_1M,  1, "Base Address 0 is below-1M: %c\n"},
    {PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_MEM_PREFETCH, 1, "Base Address 0 is prefetchable: %c\n"},
    {PCI_BASE_ADDRESS_1, PCI_BASE_ADDRESS_MEM_MASK,     0, "Base Address 1: %08x\n"},
    {PCI_BASE_ADDRESS_1, PCI_BASE_ADDRESS_SPACE,        1, "Base Address 1 Is I/O: %c\n"},
    {PCI_BASE_ADDRESS_1, PCI_BASE_ADDRESS_MEM_TYPE_64,  1, "Base Address 1 is 64-bits: %c\n"},
    {PCI_BASE_ADDRESS_1, PCI_BASE_ADDRESS_MEM_TYPE_1M,  1, "Base Address 1 is below-1M: %c\n"},
    {PCI_BASE_ADDRESS_1, PCI_BASE_ADDRESS_MEM_PREFETCH, 1, "Base Address 1 is prefetchable: %c\n"},
    {PCI_BASE_ADDRESS_2, PCI_BASE_ADDRESS_SPACE,        1, "Base Address 2 Is I/O: %c\n"},
    {PCI_BASE_ADDRESS_2, PCI_BASE_ADDRESS_MEM_TYPE_64,  1, "Base Address 2 is 64-bits: %c\n"},
    {PCI_BASE_ADDRESS_2, PCI_BASE_ADDRESS_MEM_TYPE_1M,  1, "Base Address 2 is below-1M: %c\n"},
    {PCI_BASE_ADDRESS_2, PCI_BASE_ADDRESS_MEM_PREFETCH, 1, "Base Address 2 is prefetchable: %c\n"},

#if 0
#define PCI_BASE_ADDRESS_2      0x18    /* 32 bits */
#define PCI_BASE_ADDRESS_3      0x1c    /* 32 bits */
#define PCI_BASE_ADDRESS_4      0x20    /* 32 bits */
#define PCI_BASE_ADDRESS_5      0x24    /* 32 bits */
#define PCI_CARDBUS_CIS         0x28
#endif

    {PCI_SUBSYSTEM_ID,        TWOBYTES,                0, "Subsystem id: %04x\n"},
    {PCI_SUBSYSTEM_VENDOR_ID, TWOBYTES,                0, "Subsystem vendor: %04x\n"},
    {PCI_ROM_ADDRESS,         ~0x7FF,                  0, "Rom address: %08x\n"},
    {PCI_ROM_ADDRESS,         PCI_ROM_ADDRESS_ENABLE,  1, "Rom is enabled: %c\n"},
    {PCI_INTERRUPT_PIN,       ONEBYTE,                 1, "Does generate interrupts: %c\n"},
    {PCI_INTERRUPT_LINE,      ONEBYTE,                 0, "Interrupt line (decimal): %i\n"},
    {PCI_INTERRUPT_PIN,       ONEBYTE,                 0, "Interrupt pin (decimal): %i\n"},
    {PCI_MIN_GNT,             ONEBYTE,                 0, "Min bus grant time (decimal): %i\n"},
    {PCI_MAX_LAT,             ONEBYTE,                 0, "Max bus latency acceptable (decimal): %i\n"},

    {0x00,                 0,                       0, NULL}
};


NTSTATUS PCIDetectDisplay(PPHYSICAL_ADDRESS pdisplay_address)
{
    int result, i;
    unsigned int ctrl_bus, ctrl_dev, ctrl_fn;
    unsigned int header_type_tmp;

    unsigned int tmp, vendor, device;
    unsigned char pci_data[0x40];
    pci_info* p_pci_info;

    DbgPrint("[*] Starting PCI scan\n");

    for (ctrl_bus=0; ctrl_bus<255; ctrl_bus++)
        for (ctrl_dev=0; ctrl_dev<31; ctrl_dev++)
            for (ctrl_fn=0; ctrl_fn<7; ctrl_fn++) {
                result = PCIConfRead(ctrl_bus, ctrl_dev, ctrl_fn, PCI_VENDOR_ID, 2, &vendor);
                result = PCIConfRead(ctrl_bus, ctrl_dev, ctrl_fn, PCI_DEVICE_ID, 2, &device);

                if ((vendor == 0xFFFF || device == 0xFFFF ) ||
                    (vendor == 0x0 && device == 0x0))
                    continue;

                result = PCIConfRead(ctrl_bus, ctrl_dev, ctrl_fn, PCI_HEADER_TYPE, 1, &header_type_tmp);
                header_type_tmp &= 0x7f;

                DbgPrint("[*] Found device! Vendor: %.4x device: %.4x header: %.2x\n", 
                    vendor, device, header_type_tmp);

                for (i=0; i<0x40; i++) {
                    result = PCIConfRead(ctrl_bus, ctrl_dev, ctrl_fn, i, 1, &tmp);
                    pci_data[i] = (unsigned char)(tmp & 0xff);
                }
                
                p_pci_info = (pci_info*) pci_data;
                
                
                //
                //  out put the pci info
                //
                
                {
                    struct pcidump_info *ptr;
                    unsigned int datum;

                    for (ptr = dumpinfo; ptr->string; ptr++) {
                        
                        //
                        // Perform a little-endian read of the item
                        //

                        datum = pci_data[ptr->offset]
                                | (pci_data[ptr->offset+1]<<8)
                                | (pci_data[ptr->offset+2]<<16)
                                | (pci_data[ptr->offset+3]<<24);
                        datum &= ptr->bitmask;
                        DbgPrint(ptr->string, ptr->bool ? (datum ? 'y' : 'n') : datum);
                    }
                    DbgPrint("\n");
                }

                /* If its not a display device */
                if (p_pci_info->class_base != 3)
                    continue;

                if (header_type_tmp == PCI_HEADER_TYPE_NORMAL) {
                    int i;
                    for (i=0; i<sizeof(p_pci_info->u.h0.base_registers)/sizeof(ULONG); i++) {
                        ULONG pos, flg, j;

                        pos = p_pci_info->u.h0.base_registers[i];
                        j = PCI_BASE_ADDRESS_0 + 4*i;
                        flg = pci_data[j] | (pci_data[j+1] << 8) | (pci_data[j+2] << 16) | (pci_data[j+3] << 24);
                        if (flg == 0xffffffff)
                            flg = 0;
                        if (!pos && !flg)
                            continue;
                        //
                        // if (!(flg & PCI_BASE_ADDRESS_SPACE_IO) && (flg & PCI_BASE_ADDRESS_MEM_PREFETCH)) {
                        //

                        if (!(flg & PCI_BASE_ADDRESS_SPACE_IO)) {
                            DbgPrint("[D] Prefetchable PCI memory at %.8x\n", (ULONG) (pos & PCI_BASE_ADDRESS_MEM_MASK));
                            (*pdisplay_address).LowPart = pos & PCI_BASE_ADDRESS_MEM_MASK;
                            break;
                        }
                    }
                } else if (header_type_tmp == PCI_HEADER_TYPE_BRIDGE) {
                    (*pdisplay_address).LowPart = p_pci_info->u.h1.base_registers[1];
                    (*pdisplay_address).LowPart &= ~PCI_BASE_ADDRESS_SPACE_IO;
                    (*pdisplay_address).LowPart &= PCI_BASE_ADDRESS_MEM_MASK;
                } else {
                    DbgPrint("[W] Unexpected PCI header\n");
                    continue;
                }

#if 0
                PCIGetName(vendor, device, debug, sizeof(debug));
                DbgPrint("[*] Good device '%s'\n", debug);
#endif

                return STATUS_SUCCESS;
            }

            DbgPrint("[E] PCI scan: failed\n");

            return STATUS_SUCCESS;
}